fix(environments): restore flat kwargs on create/update_environment - #201
Closed
mathieu-hai wants to merge 1 commit into
Closed
fix(environments): restore flat kwargs on create/update_environment#201mathieu-hai wants to merge 1 commit into
mathieu-hai wants to merge 1 commit into
Conversation
Adding the `desktop` kind turned the `POST /environments` and
`PUT /environments/{id}` bodies into unions discriminated on `kind`. Fern
cannot inline a union into keyword arguments, so the generated clients
collapsed to a single `request=` parameter — a breaking change that shipped
in 1.0.6 with no changelog entry:
TypeError: EnvironmentsClient.create_environment() got an
unexpected keyword argument 'id'
That broke the documented call style and every caller written against
<=1.0.5. Add a hand-written `FlatEnvironmentsClient` (and its async twin)
that collects flat fields into the union member for the requested `kind`
(`web` when omitted), and wire it in through `Client.environments`.
`request=` keeps working untouched, so this is additive. Flat fields that do
not belong to the selected kind raise `TypeError` here rather than riding
along as pydantic extras — the union members are `extra="allow"`, so a typo
would otherwise reach the server as an opaque 422.
`update_environment` resolves its path segment from the positional argument,
the flat `id`, or `request.id`, so all three historical call shapes work.
Tests assert the serialized request body rather than the model, and pin that
both call styles put the same bytes on the wire.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Le problème
Repéré par @h-julie-dujardin en testant l'intégration 1Password, et signalé dans H-Tech-Hub-docs-new#200 :
Cause racine : #161 (« Local control for user_device environments ») a ajouté le kind
desktop, ce qui a transformé les bodies dePOST /environmentsetPUT /environments/{id}en unions discriminées surkind. Fern ne sait pas inliner une union en arguments nommés, donc les clients générés se sont réduits à un seul paramètrerequest=:C'est un breaking change livré dans un bump patch (1.0.5 → 1.0.6), sans entrée de changelog. Il casse le style d'appel documenté et tout code écrit contre
<=1.0.5. Le format wire n'a pas changé — seule la signature Python a régressé.Les fichiers écrits à la main de ce repo (
client.py,polling.py,tools.py,webhook_verification.py) ne survivent pas par convention de nommage — ils sont recopiés depuissdk-codegen/python/*.py.staticpargenerate.sh, qui faitrm -rf src/hai_agentsavant de régénérer. Une correction posée uniquement ici serait donc écrasée au prochain sync.Le correctif de fond est donc hcompai/agent_platform#1550, à merger en premier. Cette PR-ci porte les mêmes fichiers côté mirror pour livrer sans attendre une régénération : le prochain sync réécrira un contenu identique. Merci @mathieudiaz pour le pointeur.
Le correctif
Un
FlatEnvironmentsClient(+ son jumeau async) qui rassemble les champs à plat dans le membre d'union correspondant aukinddemandé (webpar défaut), branché viaClient.environments.Purement additif : aucun appel existant ne change de comportement.
Pourquoi pas un fix dans le spec ou la config Fern
Aplatir le
oneOfen un objet unique donnerait des kwargs à plat natifs, sans overlay. Maiskinddeviendrait un champ optionnel ordinaire, que Fern omet quand il n'est pas fourni — et le serveur exige le tag (422 Unable to extract tag using discriminator 'kind', la discrimination tourne avant les defaults par variante). On échangerait uneTypeErrorfranche contre un 422 silencieux. Construire la classe membre est ce qui garantit que le tag part sur le wire ; c'est exactement ce quetest_create_accepts_flat_fieldsvérifie.Détails qui méritent un œil en review
TypeError. Les membres d'union sontextra="allow", donc sans ce garde-fou une faute de frappe (start_ur1=) partirait au serveur et reviendrait en 422 opaque. On restaure l'erreur de la 1.0.5 : immédiate et lisible. Idem pour un champ browser passé à un environnementdesktop.update_environmentet la collision surid. Le segment de path et le body portent tous les deux unid(d'où leid_de la 1.0.5). Le path est résolu depuis l'argument positionnel, puis leidà plat, puisrequest.id— donc les trois formes historiques passent, y comprisupdate_environment("x", id="x", ...).kind="…"inconnu lève uneTypeErrorqui pointe versrequest=comme échappatoire, plutôt que de tomber silencieusement surweb.with_raw_responsen'est pas couvert — il garde la signature généréerequest=. Volontaire : c'est la surface bas niveau. À dire si vous voulez qu'elle suive.Tests
tests/test_environments_flat.py— 21 tests viahttpx.MockTransport, qui assertent le body sérialisé et pas le modèle : un wrapper ergonomique qui perd ou mange un champ ne serait pas un correctif. Un test paramétré vérifie que les deux styles d'appel envoient exactement les mêmes octets.184 passed, 10 skippeden local avec--all-extras.tests/n'est pas dans l'arbre régénéré (generate.shne wipe quesrc/hai_agents), donc ces tests survivent aux syncs et gardent le mirror.Suites à donner
versiondanspyproject.toml, les bumps sont portés par les commits de sync.🤖 Generated with Claude Code